package asruby.runtime.scope {

import asruby.evaluator.ASTInterpreter;
import asruby.lang.RubyArray;
import asruby.parser.BlockStaticScope;
import asruby.parser.StaticScope;
import asruby.runtime.DynamicScope;
import asruby.runtime.IRubyObject;

/**
 * Represents the the dynamic portion of scoping information.  The variableValues are the
 * values of assigned local or block variables.  The staticScope identifies which sort of
 * scope this is (block or local).
 * 
 * Properties of Dynamic Scopes:
 * 1. static and dynamic scopes have the same number of names to values
 * 2. size of variables (and thus names) is determined during parsing.  So those structured do
 *    not need to change
 *
 * FIXME: When creating dynamic scopes we sometimes accidentally pass in extra parents.  This
 * is harmless (other than wasting memory), but we should not do that.  We can fix this in two
 * ways:
 * 1. Fix all callers
 * 2. Check parent that is passed in and make if new instance is local, then its parent is not local
 */
public class ManyVarsDynamicScope extends DynamicScope {
    // Our values holder (name of variables are kept in staticScope)
    private  var variableValues:Array;

    public  function ManyVarsDynamicScope(staticScope:StaticScope, parent:DynamicScope=null){
        super(staticScope, parent);
    }

    private  function lazy():void{
        if(variableValues == null) {
            var size: int =  staticScope.getNumberOfVariables();
            variableValues = new IRubyObject[size];
        }
    }
    
    public override function cloneScope():DynamicScope{
        return new ManyVarsDynamicScope(staticScope, parent);
    }

    public override function getValues():Array{
        lazy();
        return variableValues;
    }
    
    /**
     * Get value from current scope or one of its captured scopes.
     * 
     * FIXME: block variables are not getting primed to nil so we need to null check those
     *  until we prime them properly.  Also add assert back in.
     * 
     * @param offset zero-indexed value that represents where variable lives
     * @param depth how many captured scopes down this variable should be set
     * @return the value here
     */
    public override function getValue(offset:int, depth:int):IRubyObject {
        if (depth > 0) {
            return parent.getValue(offset, depth - 1);
        }
        lazy();
        //assert variableValues != null : "No variables in getValue for off: " + offset + ", Dep: " + depth;
        //assert offset < variableValues.length : "Index to big for getValue off: " + offset + ", Dep: " + depth + ", O: " + this;
        // &foo are not getting set from somewhere...I want the following assert to be true though
        //assert variableValues[offset] != null : "Getting unassigned: " + staticScope.getVariables()[offset];
        return variableValues[offset];
    }
    
    /**
     * Variation of getValue that checks for nulls, returning and setting the given value (presumably nil)
     */
    public override function getValueOrNil(offset:int, depth:int, nil:IRubyObject):IRubyObject{
        if (depth > 0) {
            return parent.getValueOrNil(offset, depth - 1, nil);
        } else {
            return getValueDepthZeroOrNil(offset, nil);
        }
    }
    
    public function getValueDepthZeroOrNil(offset:int, nil:IRubyObject):IRubyObject{
        lazy();
        //assert variableValues != null : "No variables in getValue for off: " + offset + ", Dep: " + 0;
        //assert offset < variableValues.length : "Index to big for getValue off: " + offset + ", Dep: " + 0 + ", O: " + this;
        // &foo are not getting set from somewhere...I want the following assert to be true though
        //assert variableValues[offset] != null : "Getting unassigned: " + staticScope.getVariables()[offset];
        var value: IRubyObject =  variableValues[offset];
        if (value == null) {
            variableValues[offset] = nil;
            value = nil;
        }
        return value;
    }
    public  function getValueZeroDepthZeroOrNil(nil:IRubyObject):IRubyObject{
        lazy();
        //assert variableValues != null : "No variables in getValue for off: " + 0 + ", Dep: " + 0;
        //assert 0 < variableValues.length : "Index to big for getValue off: " + 0 + ", Dep: " + 0 + ", O: " + this;
        // &foo are not getting set from somewhere...I want the following assert to be true though
        //assert variableValues[offset] != null : "Getting unassigned: " + staticScope.getVariables()[offset];
        var value: IRubyObject =  variableValues[0];
        if (value == null) {
            variableValues[0] = nil;
            value = nil;
        }
        return value;
    }
    public  function getValueOneDepthZeroOrNil(nil:IRubyObject):IRubyObject{
        lazy();
        //assert variableValues != null : "No variables in getValue for off: " + 1 + ", Dep: " + 0;
        //assert 1 < variableValues.length : "Index to big for getValue off: " + 1 + ", Dep: " + 0 + ", O: " + this;
        // &foo are not getting set from somewhere...I want the following assert to be true though
        //assert variableValues[offset] != null : "Getting unassigned: " + staticScope.getVariables()[offset];
        var value: IRubyObject =  variableValues[1];
        if (value == null) {
            variableValues[1] = nil;
            value = nil;
        }
        return value;
    }

    /**
     * Set value in current dynamic scope or one of its captured scopes.
     * 
     * @param offset zero-indexed value that represents where variable lives
     * @param value to set
     * @param depth how many captured scopes down this variable should be set
     */
    public override function setValue(offset:int, value:IRubyObject, depth:int):void{
        if (depth > 0) {
            //assert parent != null : "If depth > 0, then parent should not ever be null";
            
            parent.setValue(offset, value, depth - 1);
        } else {
            lazy();
            //assert offset < variableValues.length : "Setting " + offset + " to " + value + ", O: " + this; 
            
            variableValues[offset] = value;
        }
    }

    public  function setValueDepthZero(value:IRubyObject, offset:int):void{
        lazy();
        //assert offset < variableValues.length : "Setting " + offset + " to " + value + ", O: " + this; 

        variableValues[offset] = value;
    }
    public  function setValueZeroDepthZero(value:IRubyObject):void{
        lazy();
        //assert 0 < variableValues.length : "Setting " + 0 + " to " + value + ", O: " + this; 

        variableValues[0] = value;
    }
    public  function setValueOneDepthZero(value:IRubyObject):void{
        lazy();
        //assert 1 < variableValues.length : "Setting " + 1 + " to " + value + ", O: " + this; 

        variableValues[1] = value;
    }

    /**
     * Set all values which represent 'normal' parameters in a call list to this dynamic
     * scope.  Function calls bind to local scopes by assuming that the indexes or the
     * arg list correspond to that of the local scope (plus 2 since $_ and $~ always take
     * the first two slots).  We pass in a second argument because we sometimes get more
     * values than we are expecting.  The rest get compacted by original caller into 
     * rest args.
     * 
     * @param values up to size specified to be mapped as ordinary parm values
     * @param size is the number of values to assign as ordinary parm values
     */
    public override function setArgValues(values:Array, size:int):void{
        lazy();
        values = variableValues.slice(0, size);
    }

    /**
     * Copy variable values back for ZSuper call.
     */
    public override function getArgValues():Array{
        // if we're not the "argument scope" for zsuper, try our parent
        if (!staticScope.isArgumentScope()) {
            return parent.getArgValues();
        }
        lazy();
        var totalArgs: int =  staticScope.getRequiredArgs() + staticScope.getOptionalArgs();
        
        // copy and splat arguments out of the scope to use for zsuper call
         var argValues: Array;
        if (staticScope.getRestArg() < 0) {
            // required and optional only
            argValues =  variableValues.slice(0, totalArgs);
            return argValues;
        } else {
            // rest arg must be splatted
            var restArg: IRubyObject =  getValue(staticScope.getRestArg(), 0);
            //assert restArg != null;
            
            // FIXME: not very efficient
            var splattedArgs: RubyArray =  ASTInterpreter.splatValue(restArg.getRuntime(), restArg);            
            argValues = variableValues.slice(0, totalArgs);
            for( var i:int = totalArgs; i<splattedArgs.size(); i++) {
            	argValues[i] = splattedArgs[i]
            }
            //System.arraycopy(splattedArgs.toJavaArray(), 0, argValues, totalArgs, splattedArgs.size());
            
            return argValues;
        }
    }

    /**
     * 
     * Make a larger dynamic scope if the static scope grew.
     * 
     * Eval's with bindings require us to possibly change the size of the dynamic scope if
     * things like 'eval "b = 2", binding' happens.
     *
     */
    public  function growIfNeeded():void{
        lazy();
        var dynamicSize: int =  variableValues == null ? 0: variableValues.length;
        
        if (staticScope.getNumberOfVariables() > dynamicSize) {
            var values:Array = new Array();
            
            if (dynamicSize > 0) {
                values.concat(variableValues, dynamicSize);
            }
            
            variableValues = values;
        }
    }

    // Helper function to give a good view of current dynamic scope with captured scopes
    public override function toString():String{
        lazy();
        var buf:String = new String();
        var indent:String = "";
        buf.concat(indent).concat("Static Type[" + this.toString() + "]: " + 
                 (staticScope is BlockStaticScope ? "block" : "local")+" [");
        var size: int =  staticScope.getNumberOfVariables();
        
        if (size != 0) {
            var names:Array = staticScope.getVariables();
            for ( var i: int = 0; i < size-1; i++) {
                buf.concat(names[i]).concat("=");

                if (variableValues[i] == null) {
                    buf.concat("null");
                } else {
                    buf.concat(variableValues[i]);
                }
                
                buf.concat(",");
            }
            buf.concat(names[size-1]).concat("=");
            
            //var variableValues.length: assert = = names.length : "V: " + variableValues.length + 
            //    " != N: " + names.length + " for " + buf;
            
            if (variableValues[size-1] == null) {
                buf.concat("null");
            } else {
                buf.concat(variableValues[size-1]);
            }
            
        }
        
        buf.concat("]");
        if (parent != null) {
            buf.concat("\n");
            buf.concat(parent.toString());
        }
        
        return buf.toString();
    }
}
}